/* picoc hash table module. This hash table code is used for both symbol tables
 * and the shared string table. */

#include "table.h"
#include "variable.h"
#include <string.h>
#include "platform.h"
/* initialize the shared string system */
void MyTable::TableInit(Interpreter *pc) {
    TableInitTable(&pc->stringTable, &pc->stringHashTable[0],
            STRING_TABLE_SIZE, true);
    pc->strEmpty = TableStrRegister(pc, "");
}

/* hash function for strings */
unsigned int MyTable::TableHash(const char *key, int len) {
    unsigned int hash = len;
    int offset;
    int i;

    for (i = 0, offset = 8; i < len; i++, offset+=7) {
        if (offset > sizeof(unsigned int) * 8 - 7)
            offset -= sizeof(unsigned int) * 8 - 6;

        hash ^= *key++ << offset;
    }

    return hash;
}

/* initialize a table */
void MyTable::TableInitTable(Table *tbl, TableEntry **hashTable, int size,
    int onHeap) {
    tbl->size = size;
    tbl->onHeap = onHeap;
    tbl->hashTable = hashTable;
    memset((void*)hashTable, '\0', sizeof(TableEntry*) * size);
}

/* check a hash table entry for a key */
TableEntry *MyTable::TableSearch(Table *tbl, const char *key,
    int *addAt) {
    /* shared strings have unique addresses so we don't need to hash them */
    int hashValue = ((unsigned long)key) % tbl->size;
    TableEntry *entry;

    for (entry = tbl->hashTable[hashValue]; entry != NULL; entry = entry->next) {
        if (entry->p.v.key == key)
            return entry;   /* found */
    }

    *addAt = hashValue;    /* didn't find it in the chain */
    return NULL;
}

/* set an identifier to a Value. returns FALSE if it already exists.
 * key must be a shared string from TableStrRegister() */
int MyTable::TableSet(Interpreter *pc, Table *tbl, char *key, Value *val,
    const char *declFileName, int declLine, int declColumn) {
    int addAt;
    TableEntry *foundEntry = TableSearch(tbl, key, &addAt);

    if (foundEntry == NULL) {   /* add it to the table */
        TableEntry *NewEntry = (TableEntry *)Variable::VariableAlloc(pc, NULL,
            sizeof(TableEntry), tbl->onHeap);
        NewEntry->declFileName = declFileName;
        NewEntry->declLine = declLine;
        NewEntry->declColumn = declColumn;
        NewEntry->p.v.key = key;
        NewEntry->p.v.val = val;
        NewEntry->next = tbl->hashTable[addAt];
        tbl->hashTable[addAt] = NewEntry;
        return true;
    }

    return false;
}

/* find a Value in a table. returns FALSE if not found.
 * key must be a shared string from TableStrRegister() */
int MyTable::TableGet(Table *tbl, const char *key, Value **val,
    const char **declFileName, int *declLine, int *declColumn) {
    int addAt;
    TableEntry *foundEntry = TableSearch(tbl, key, &addAt);
    if (foundEntry == NULL)
        return false;

    *val = foundEntry->p.v.val;

    if (declFileName != NULL) {
        *declFileName = foundEntry->declFileName;
        *declLine = foundEntry->declLine;
        *declColumn = foundEntry->declColumn;
    }

    return true;
}

/* remove an entry from the table */
Value *MyTable::TableDelete(Interpreter *pc, Table *tbl, const char *key) {
    /* shared strings have unique addresses so we don't need to hash them */
    int hashValue = ((unsigned long)key) % tbl->size;
    TableEntry **entryPtr;

    for (entryPtr = &tbl->hashTable[hashValue];
            *entryPtr != NULL; entryPtr = &(*entryPtr)->next) {
        if ((*entryPtr)->p.v.key == key) {
            TableEntry *DeleteEntry = *entryPtr;
            Value *val = DeleteEntry->p.v.val;
            *entryPtr = DeleteEntry->next;
            Interpreter::HeapFreeMem(DeleteEntry);

            return val;
        }
    }

    return NULL;
}

/* check a hash table entry for an identifier */
TableEntry *MyTable::TableSearchIdentifier(Table *tbl,
    const char *key, int len, int *addAt) {
    int hashValue = TableHash(key, len) % tbl->size;
    TableEntry *entry;

    for (entry = tbl->hashTable[hashValue]; entry != NULL; entry = entry->next) {
        if (strncmp(&entry->p.key[0], (char*)key, len) == 0 &&
                entry->p.key[len] == '\0')
            return entry;   /* found */
    }

    *addAt = hashValue;    /* didn't find it in the chain */
    return NULL;
}

/* set an identifier and return the identifier. share if possible */
char *MyTable::TableSetIdentifier(Interpreter *pc, Table *tbl, const char *Ident,
    int identLen) {
    int addAt;
    TableEntry *foundEntry = TableSearchIdentifier(tbl, Ident, identLen,
        &addAt);

    if (foundEntry != NULL)
        return &foundEntry->p.key[0];
    else {
        /* add it to the table - we economise by not allocating
            the whole structure here */
        TableEntry *NewEntry = (TableEntry *)Interpreter::HeapAllocMem(sizeof(TableEntry) -
            sizeof(union TableEntry::TableEntryPayload) + identLen + 1);
        if (NewEntry == NULL)
            Platform::ProgramFailNoParser(pc, "(TableSetIdentifier) out of memory");

        strncpy((char *)&NewEntry->p.key[0], (char *)Ident, identLen);
        NewEntry->p.key[identLen] = '\0';
        NewEntry->next = tbl->hashTable[addAt];
        tbl->hashTable[addAt] = NewEntry;
        return &NewEntry->p.key[0];
    }
}

/* register a string in the shared string store */
char *MyTable::TableStrRegister2(Interpreter *pc, const char *str, int len) {
    return TableSetIdentifier(pc, &pc->stringTable, str, len);
}

char *MyTable::TableStrRegister(Interpreter *pc, const char *str) {
    return TableStrRegister2(pc, str, strlen((char *)str));
}

/* free all the strings */
void MyTable::TableStrFree(Interpreter *pc) {
    TableEntry *entry;
    TableEntry *nextEntry;

    for (int i = 0; i < pc->stringTable.size; i++) {
        for (entry = pc->stringTable.hashTable[i];
                entry != NULL; entry = nextEntry) {
            nextEntry = entry->next;
            Interpreter::HeapFreeMem(entry);
        }
    }
}
